package strauss.flatland.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.util.List;
import java.util.concurrent.TimeUnit;

import strauss.flatland.model.FlatLandModel;
import strauss.flatland.things.Dot;
import strauss.flatland.things.IThing;
import strauss.flatland.things.Line;
import strauss.flatland.things.geometry.Vertex;

/**
 * 
 * @author sybrand
 *
 */
public class FlatLandComponent extends AbstractFlatLandComponent implements Runnable {
	private static final long serialVersionUID = 1670882807699516881L;
	private FlatLandModel model;
	private Image image;
	private Graphics offscreen;
	private int imageWidth;
	private int imageHeight;
	private Thread updateThread;
	private boolean running;
	private final int TARGET_FPS = 25;
	private final int TARGET_SLEEP = 1000/TARGET_FPS;
	
	public FlatLandComponent() {
		updateThread = new Thread(this);
		setRunning(true);
		updateThread.start();
	}
	
	private synchronized void setRunning(boolean running) {
		this.running=running;
	}
	
	public void stop() {
		setRunning(false);
		try {
			updateThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void run() {
		//FPS fps = new FPS(TARGET_FPS);
		//fps.reset();
		while (running) {
			try {
				// put code here, that waits for model to signal it 
				// has updated, the longest we'll wait, is a second
				// then we update anyway
				model.getSemaphore().tryAcquire(1, 1, TimeUnit.SECONDS);
				long startTime = System.currentTimeMillis();
				//fps.tick();
				repaint();
				long delta = System.currentTimeMillis()-startTime;
				Thread.sleep(Math.max(0, TARGET_SLEEP-delta));
			} catch (InterruptedException e) {
				setRunning(false);
			}
		}
	}
	
	private void updateOffScreenBuffer(Graphics g) {
		// dirty triangles be damned - right now, we just
		// repaint the whole thing
		g.clearRect(0, 0, imageWidth, imageHeight);

		if (model != null) {
			List<IThing> things = model.getThings();
			for (IThing thing : things) {
				g.setColor(Color.BLACK);
				if (thing instanceof Dot) {
					Dot myDot = (Dot)thing;
					g.setColor(myDot.getColor());
					for (Vertex coord : thing.getCoordinates()) {
						// we draw a dot bigger than it really is, it's nicer
						// than having pixels go around. hence the circle
						double diam = 2;
						double x = coord.getX() - diam/2;
						double y = coord.getY() - diam/2;
						g.fillOval((int)x, (int)y, (int)diam, (int)diam);
					}
				} else if (thing instanceof Line) {
					Line line = (Line) thing;
					g.drawLine((int)line.getStart().getX(), (int)line.getStart().getY(),
							(int)line.getEnd().getX(), (int)line.getEnd().getY());
					line.getEnd();
				}
			}
		}
	}
	
	public void paint(Graphics g) {
		update(g);
	}
	
	public void update(Graphics g) {

		if (image == null || imageWidth != g.getClipBounds().width
				|| imageHeight != g.getClipBounds().height) {
			// if we have lost the image, or the image dimensions don't
			// agree with the image we have, then create a new image
			imageWidth = g.getClipBounds().width;
			imageHeight = g.getClipBounds().height;
			image = createImage(imageWidth, imageHeight);
			offscreen = image.getGraphics();
		}
		// we update the offscreen buffer, we will blit
		// it to the front when we are good and ready
		updateOffScreenBuffer(offscreen);
		// double buffering
		g.drawImage(image, 0, 0, this);
	}

	public void setModel(FlatLandModel model) {
		this.model = model;
	}
}